package com.sh.data.engine.domain.workspace.manager.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.sh.data.engine.common.constants.HttpStatus;
import com.sh.data.engine.common.constants.code.BizCodes;
import com.sh.data.engine.common.enumDefinition.DSType;
import com.sh.data.engine.common.exception.BusinessException;
import com.sh.data.engine.common.util.RSAUtil;
import com.sh.data.engine.domain.base.model.PageResult;
import com.sh.data.engine.domain.base.model.UserContext;
import com.sh.data.engine.domain.base.model.enums.Database;
import com.sh.data.engine.domain.integration.datasource.model.domain.DataSourceDomain;
import com.sh.data.engine.domain.integration.datasource.model.param.DataSourceSaveParam;
import com.sh.data.engine.domain.integration.datasource.service.DataSourceService;
import com.sh.data.engine.domain.shims.DbManagerFactory;
import com.sh.data.engine.domain.shims.db.BaseDbManager;
import com.sh.data.engine.domain.shims.db.DbOptions;
import com.sh.data.engine.domain.shims.saphana.manager.SapManager;
import com.sh.data.engine.domain.workspace.index.service.ProjectService;
import com.sh.data.engine.domain.workspace.manager.model.domain.StorageDetailDomain;
import com.sh.data.engine.domain.workspace.manager.model.domain.StorageListDomain;
import com.sh.data.engine.domain.workspace.manager.model.mapper.StorageListDomainMapper;
import com.sh.data.engine.domain.workspace.manager.model.param.StorageConnectionParam;
import com.sh.data.engine.domain.workspace.manager.model.param.StorageSaveParam;
import com.sh.data.engine.domain.workspace.manager.service.StorageService;
import com.sh.data.engine.repository.dao.workspace.index.entity.ProjectEntity;
import com.sh.data.engine.repository.dao.workspace.manager.StorageMapper;
import com.sh.data.engine.repository.dao.workspace.manager.entity.StorageEntity;
import com.sh.data.engine.repository.dao.workspace.manager.entity.StorageEntity.Rmdbs;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.net.URLEncoder;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: mengzheng.mhc
 * @date: 2024/6/12 17:08
 */
@Slf4j
@Service
public class StorageServiceImpl extends ServiceImpl<StorageMapper, StorageEntity> implements StorageService {

    @Lazy
    @Autowired
    private ProjectService projectService;

    @Lazy
    @Autowired
    private DataSourceService dataSourceService;

    @Lazy
    @Autowired
    private StorageListDomainMapper storageListDomainMapper;

    @Override
    @Transactional(rollbackFor = Exception.class, transactionManager = "engineTransactionManager")
    public void saveStorage(StorageSaveParam storageParam) {
        // 1.校验数据
        isStorageNameExist(storageParam);

        // 2. 判断连接
        int status = 1;
        String dbVersion = null;

        BaseDbManager dbManager = getDbManager(
            storageParam.getStorageType(),
            storageParam.getUsername(),
            storageParam.getPassword(),
            storageParam.getStorageLink(),
            storageParam.getHiveSiteAddress());

        if (dbManager != null) {
            try {
                dbVersion = dbManager.getDbVersion();
                dbManager.testConnection();
            } catch (SQLException e) {
                status = 0;
                log.error(e.getMessage());
            }
        } else {
            status = 0;
        }

        String storageType = storageParam.getStorageType();
        if (storageType.equals("SAP")) {
            storageParam.setSchema(storageParam.getDbName());
        }
        StorageEntity.Rmdbs rmdbs =
            Rmdbs.builder()
                .username(storageParam.getUsername())
                .password(storageParam.getPassword())
                .version(dbVersion)
                .server(storageParam.getServer())
                .port(storageParam.getPort())
                .dbName(storageParam.getDbName())
                .hiveSiteXmlPath(storageParam.getHiveSiteAddress())
                .originalFilename(storageParam.getOriginalFilename())
                .schema(storageParam.getSchema())
                .connectionProperty(storageParam.getConnectionProperty())
                .build();

        StorageEntity storageEntity =
            StorageEntity.builder()
                .storageName(storageParam.getStorageName())
                .storageType(storageParam.getStorageType())
                .status(status)
                .rowState(1)
                .statusUpdateTime(new Date())
                .storageDesc(storageParam.getStorageDesc())
                .storageLink(storageParam.getStorageLink())
                .rmdbs(rmdbs)
                .build();

        storageEntity.setRmdbsInfo();
        Long storageId = storageParam.getStorageId();
        if (storageId != null) {
            storageEntity.setId(storageId);
            this.updateById(storageEntity);

            // 查询是否有依赖的数据源
            List<ProjectEntity> projectEntities = projectService.getByStorageId(storageId);
            List<Long> projectIds =
                projectEntities.stream().map(e -> e.getId()).collect(Collectors.toList());

            List<DataSourceDomain> systemDataSources =
                dataSourceService.getSystemDataSourceByProjectIds(projectIds);

            StorageDetailDomain detailStorage = getDetailStorage(storageId, false);

            DataSourceSaveParam dataSourceParam = detailStorage.convertToDataSourceSaveParam();

            for (DataSourceDomain dataSourceDomain : systemDataSources) {

                dataSourceParam.setProjectId(dataSourceDomain.getProjectId());
                dataSourceParam.setId(dataSourceDomain.getId());

                dataSourceService.saveDataSource(dataSourceParam);
            }
            return;
        }
        this.save(storageEntity);
    }

    @Override
    public List<StorageEntity> findByIds(Collection<Long> storageIds) {
        return listByIds(storageIds);
    }

    @Override
    public void deleteStorage(Long storageId) {
        Assert.notNull(storageId, "storageId can't be null");

        StorageEntity storageEntityById =
            this.baseMapper.selectOne(
                new LambdaQueryWrapper<StorageEntity>()
                    .eq(StorageEntity::getId, storageId)
                    .eq(StorageEntity::getRowState, 1));

        if (storageEntityById == null) {
            throw new BusinessException(BizCodes.STORAGE_NOT_FOUND, HttpStatus.BAD_REQUEST);
        }

        final List<ProjectEntity> projectEntityList = projectService.getByStorageId(storageId);
        List<ProjectEntity> projectEntities =
            projectEntityList.stream()
                .filter(project -> project.getRowState() == 1)
                .collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(projectEntities)) {
            throw new BusinessException("该存储正在被项目引用，无法删除");
        }

        storageEntityById.setRowState(0);
        storageEntityById.setCreatorId(UserContext.getUserId());
        storageEntityById.setUpdateTime(new Date());
        this.updateById(storageEntityById);
    }

    @Override
    public Boolean testStorage(StorageConnectionParam storageConnectionParam) {
        Long storageId = storageConnectionParam.getStorageId();
        String storageLink = storageConnectionParam.getStorageLink();
        String storageType = storageConnectionParam.getStorageType();
        String username = storageConnectionParam.getUsername();
        String password = storageConnectionParam.getPassword();
        String hiveSiteXmlPath = storageConnectionParam.getHiveSiteAddress();
        String sapSchema = storageConnectionParam.getSapSchema();
        String jdbcTypeByUrl = getJDBCTypeByUrl(storageLink);

        if (!storageLink.startsWith("jdbc:")) {
            throw new BusinessException(BizCodes.INVALID_JDBC_URL_ERROR, HttpStatus.BAD_REQUEST);
        }

        if (storageType.equals(DSType.Hive.name())) {
            if (!"hive2".equalsIgnoreCase(jdbcTypeByUrl)) {
                throw new BusinessException(BizCodes.UNSUPPORTED_HIVE_CONNECTION, HttpStatus.BAD_REQUEST);
            }
        }

        if (requireJdbcUrlTypeCheck(storageType) && DSType.isRdbms(storageType)) {
            if (!jdbcTypeByUrl.equalsIgnoreCase(storageType)
                && (DSType.MatrixDB.toString().equalsIgnoreCase(storageType)
                && !DSType.PostgreSQL.toString().equalsIgnoreCase(jdbcTypeByUrl))) {
                throw new BusinessException(BizCodes.MISMATCH_STORAGE_AND_JDBC_URL, HttpStatus.BAD_REQUEST);
            }
        }
        BaseDbManager baseDbManager = null;
        try {
            baseDbManager = getDbManager(storageType, username, password, storageLink, hiveSiteXmlPath);
        } catch (Exception e) {
            if (storageId != null) {
                StorageEntity storageMapperById = this.baseMapper.selectById(storageId);
                storageMapperById.setStatus(0);
                this.updateById(storageMapperById);
            }
            log.error(e.getMessage());
            throw new BusinessException(BizCodes.DS_CONNECTION_FAILED, HttpStatus.BAD_REQUEST);
        }

        try {

            baseDbManager.testConnection();
            if (DSType.Sap.name().equals(storageType)) {
                SapManager sap = (SapManager) baseDbManager;
                Boolean check = sap.checkSchema(sapSchema);
                if (!check) {
                    throw new BusinessException(BizCodes.SCHEMA_NOT_FOUND, HttpStatus.BAD_REQUEST);
                }
            }
        } catch (SQLException e) {
            if (storageId != null) {
                StorageEntity storageMapperById = getById(storageId);
                storageMapperById.setStatus(0);
                this.updateById(storageMapperById);
            }
            log.error(e.getMessage());
            throw new BusinessException(BizCodes.DS_CONNECTION_FAILED, HttpStatus.BAD_REQUEST);
        }

        if (storageId != null) {
            StorageEntity storageMapperById = baseMapper.selectById(storageId);
            storageMapperById.setStatus(1);
            updateById(storageMapperById);
        }
        return true;
    }

    @Override
    public StorageDetailDomain getDetailStorage(Long storageId, boolean encrypt) {
        StorageEntity storageById = baseMapper.selectById(storageId);

        if (storageById == null || storageById.getRowState().equals(0)) {
            throw new BusinessException(BizCodes.STORAGE_NOT_FOUND, HttpStatus.BAD_REQUEST);
        }
        StorageDetailDomain detailDomain = new StorageDetailDomain();
        detailDomain.setId(storageId);
        detailDomain.setStorageType(storageById.getStorageType());
        detailDomain.setStorageName(storageById.getStorageName());
        detailDomain.setStorageDesc(storageById.getStorageDesc());
        detailDomain.setServer(storageById.getRmdbs().getServer());
        detailDomain.setPort(storageById.getRmdbs().getPort());
        detailDomain.setUsername(storageById.getRmdbs().getUsername());
        detailDomain.setDbName(storageById.getRmdbs().getDbName());
        detailDomain.setSchema(storageById.getRmdbs().getSchema());
        detailDomain.setOriginalFilename(storageById.getRmdbs().getOriginalFilename());
        detailDomain.setHiveSiteAddress(storageById.getRmdbs().getHiveSiteXmlPath());
        detailDomain.setStorageLink(storageById.getStorageLink());
        detailDomain.setConnectionProperty(storageById.getRmdbs().getConnectionProperty());
        detailDomain.setStatus(storageById.getStatus());
        String password = storageById.getRmdbs().getPassword();
        detailDomain.setPassword(password);
        if (encrypt && StringUtils.isNotBlank(password)) {
            try {
                password = URLEncoder.encode(password, "UTF-8");
                detailDomain.setPassword(RSAUtil.encryptByPublicKey(password));
            } catch (Exception e) {
                log.error(e.getMessage());
                throw new BusinessException(BizCodes.ENCRYPT_PASSWORD_FAILED, HttpStatus.BAD_REQUEST);
            }
        }

        return detailDomain;
    }

    @Override
    public PageResult<StorageListDomain> listStorage(Integer pageNum, Integer pageSize, Long lastStorageId, String dsType, String search) {
        LambdaQueryWrapper<StorageEntity> eq =
            new LambdaQueryWrapper<StorageEntity>()
                .eq(StorageEntity::getRowState, 1)
                .eq(StringUtils.isNotBlank(dsType), StorageEntity::getStorageType, dsType)
                .orderByDesc(StorageEntity::getUpdateTime);

        if (StringUtils.isNotBlank(search)) {
            eq.and(
                e ->
                    e.like(StorageEntity::getStorageName, search)
                        .or()
                        .like(StorageEntity::getStorageLink, search));
        }
        IPage<StorageEntity> storageEntityIPage = page(new Page<>(pageNum, pageSize), eq);

        PageResult<StorageListDomain> r1 =
            PageResult.copyPropertiesAndConvert(storageEntityIPage, StorageListDomain.class);

        return r1;
    }

    @Override
    public List<String> getSchemas(String storageLink, String username, String password, Integer dsType) {
        List<String> schemas = Lists.newArrayList();
        if (dsType == 3) {
            BaseDbManager baseDbManager =
                getDbManager(DSType.KINGBASE8.name(), username, password, storageLink, "");
            try {
                baseDbManager.testConnection();
            } catch (SQLException e) {
                throw new BusinessException(e.getMessage());
            } finally {
                baseDbManager.close();
            }
            try {
                schemas = baseDbManager.getSchemaList();
            } catch (SQLException e) {
                throw new BusinessException(e.getMessage());
            } finally {
                baseDbManager.close();
            }
        } else if (dsType == 0 || dsType == 2) {
            BaseDbManager baseDbManager =
                getDbManager(DSType.PostgreSQL.name(), username, password, storageLink, "");
            try {
                baseDbManager.testConnection();
            } catch (SQLException e) {
                throw new BusinessException(e.getMessage());
            } finally {
                baseDbManager.close();
            }
            try {
                schemas = baseDbManager.getSchemaList();
            } catch (SQLException e) {
                throw new BusinessException(e.getMessage());
            } finally {
                baseDbManager.close();
            }
        } else {
            BaseDbManager baseDbManager =
                getDbManager(DSType.Sap.name(), username, password, storageLink, "");
            //      try {
            //        baseDbManager.testConnection();
            //      } catch (SQLException e) {
            //        throw new BusinessException(StatusCode.FAILED, e.getMessage());
            //      } finally {
            //        baseDbManager.close();
            //      }
            try {
                schemas = baseDbManager.getSchemaList();
                // schemas.add("PUBLIC");
            } catch (SQLException e) {
                throw new BusinessException(e.getMessage());
            } finally {
                baseDbManager.close();
            }
        }
        return schemas;
    }

    @Override
    public StorageEntity tmpGetStorageInfoByProjectId(Long projectId) {
        ProjectEntity project = projectService.getProjectById(projectId);
        if (project == null) {
            return null;
        }
        return getById(project.getStorageId());
    }

    @Override
    public List<StorageEntity> findStorageIsUsable() {
        return this.list(new LambdaQueryWrapper<StorageEntity>().eq(StorageEntity::getRowState, 1));
    }

    @Override
    public void updateStatusByIds(int status, List<Long> multiStorageId) {
        this.update(
            new LambdaUpdateWrapper<StorageEntity>()
                .in(StorageEntity::getId, multiStorageId)
                .set(StorageEntity::getStatus, status));
    }

    @Override
    public List<StorageListDomain> getAllStorageList() {

        final List<StorageEntity> entities = list();

        return storageListDomainMapper.map(entities);
    }

    public StorageEntity getById(String storageId) {
        return getBaseMapper().selectById(storageId);
    }

    public StorageEntity getStorageInfoByProjectId(Long projectId) {
        ProjectEntity project = projectService.getProjectById(projectId);
        StorageEntity storage = getById(project.getStorageId());
        return storage;
    }

    /**
     * description 判断存储源名称是否重复
     *
     * @author : mingxi.xpb
     * @date : 2022/1/12 15:17
     */
    private void isStorageNameExist(StorageSaveParam param) {
        LambdaQueryWrapper<StorageEntity> lambdaQueryWrapper =
            new LambdaQueryWrapper<StorageEntity>()
                .eq(
                    StringUtils.isNotBlank(param.getStorageName()),
                    StorageEntity::getStorageName,
                    param.getStorageName())
                .ne(Objects.nonNull(param.getStorageId()), StorageEntity::getId, param.getStorageId());

        if (CollectionUtils.isNotEmpty(this.list(lambdaQueryWrapper))) {
            throw new BusinessException(BizCodes.STORAGE_NAME_ALREADY_EXISTS_ERROR, HttpStatus.BAD_REQUEST);
        }
    }

    /**
     * 根据url获取数据库类型
     *
     * @param dsLink
     * @return
     */
    private String getJDBCTypeByUrl(String dsLink) {
        String jdbcType = "";

        String beginPos = "jdbc:";

        if (!StringUtils.isEmpty(dsLink)) {
            int start = beginPos.length();
            int end = dsLink.indexOf(":", start + 1);
            jdbcType = dsLink.substring(start, end);
        }

        return jdbcType;
    }

    /**
     * skip the jdbcurl type check
     *
     * @param type
     * @return
     */
    private boolean requireJdbcUrlTypeCheck(String type) {
        List<DSType> skipList = Collections.singletonList(DSType.TiDB);
        for (DSType dsType : skipList) {
            if (type.equalsIgnoreCase(dsType.name())) {
                return false;
            }
        }
        return true;
    }

    /**
     * description 获取DbManager
     *
     * @author : mingxi.xpb
     * @date : 2022/1/14 11:25
     */
    private BaseDbManager getDbManager(
        String storageType,
        String username,
        String password,
        String storageLink,
        String hiveSiteXmlPath) {
        Database database = Database.from(storageType);
        DbOptions dbOptions = new DbOptions(database, username, password, storageLink, hiveSiteXmlPath);
        try {
            return DbManagerFactory.getDbManager(database, dbOptions);
        } catch (Exception e) {
            log.error("failed to create DbManager,please check your link information!");
            return null;
        }
    }
}
